/*
 *  ETA/OS - Python VM
 *  Copyright (C) 2017   Michel Megens <dev@bietje.net>
 *  Copyright (C) 2017   Dean Hall
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#undef __FILE_ID__
#define __FILE_ID__ 0x09

/**
 * \file
 * \brief VM Interpreter
 *
 * VM interpreter operations.
 */

#include <etaos/python.h>
#include <etaos/preempt.h>

#define PM_PREEMPT_ENABLE_BREAK(value) \
	if(value != PM_RET_OK) { \
		preempt_enable_no_resched(); \
		break; \
	}

PmReturn_t interpret(const uint8_t returnOnNoThreads)
{
	PmReturn_t retval = PM_RET_OK;
	pPmObj_t pobj1 = C_NULL;
	pPmObj_t pobj2 = C_NULL;
	pPmObj_t pobj3 = C_NULL;
	int16_t t16 = 0;
	int16_t t8 = 0;
	uint8_t bc;
	uint8_t objid, objid2;
	int i;

	/* Activate a thread the first time */
	retval = interp_reschedule();
	PM_RETURN_IF_ERROR(retval);

	/* Interpret loop */
	for (;;) {
		if (gVmGlobal.pthread == C_NULL) {
			if (returnOnNoThreads) {
				/* User chose to return on no threads left */
				return retval;
			}

			/*
			 * Without a frame there is nothing to execute, so reschedule
			 * (possibly activating a recently added thread).
			 */
			retval = interp_reschedule();
			PM_BREAK_IF_ERROR(retval);
			continue;
		}

		/* Reschedule threads if flag is true? */
		if (gVmGlobal.reschedule) {
			retval = interp_reschedule();
			PM_BREAK_IF_ERROR(retval);
		}

		/* Get byte; the func post-incrs PM_IP */
		bc = mem_getByte(PM_FP->fo_memspace, &PM_IP);
		switch (bc) {
		case POP_TOP:
			pobj1 = PM_POP();
			continue;

		case ROT_TWO:
			pobj1 = TOS;
			TOS = TOS1;
			TOS1 = pobj1;
			continue;

		case ROT_THREE:
			pobj1 = TOS;
			TOS = TOS1;
			TOS1 = TOS2;
			TOS2 = pobj1;
			continue;

		case DUP_TOP:
			pobj1 = TOS;
			PM_PUSH(pobj1);
			continue;

		case ROT_FOUR:
			pobj1 = TOS;
			TOS = TOS1;
			TOS1 = TOS2;
			TOS2 = TOS3;
			TOS3 = pobj1;
			continue;

		case NOP:
			continue;

		case UNARY_POSITIVE:
			/* Raise TypeError if TOS is not an int */
			if ((OBJ_GET_TYPE(TOS) != OBJ_TYPE_INT)
#ifdef HAVE_FLOAT
			    && (OBJ_GET_TYPE(TOS) != OBJ_TYPE_FLT)
#endif				/* HAVE_FLOAT */
			    ) {
				PM_RAISE(retval, PM_RET_EX_TYPE);
				break;
			}

			/* When TOS is an int, this is a no-op */
			continue;

		case UNARY_NEGATIVE:
#ifdef HAVE_FLOAT
			if (OBJ_GET_TYPE(TOS) == OBJ_TYPE_FLT) {
				retval = float_negative(TOS, &pobj2);
			} else
#endif				/* HAVE_FLOAT */
			{
				retval = int_negative(TOS, &pobj2);
			}
			PM_BREAK_IF_ERROR(retval);
			TOS = pobj2;
			continue;

		case UNARY_NOT:
			pobj1 = PM_POP();
			if (obj_isFalse(pobj1)) {
				PM_PUSH(PM_TRUE);
			} else {
				PM_PUSH(PM_FALSE);
			}
			continue;

#ifdef HAVE_BACKTICK
			/* #244 Add support for the backtick operation (UNARY_CONVERT) */
		case UNARY_CONVERT:
			retval = obj_repr(TOS, &pobj3);
			PM_BREAK_IF_ERROR(retval);
			TOS = pobj3;
			continue;
#endif				/* HAVE_BACKTICK */

		case UNARY_INVERT:
			/* Raise TypeError if it's not an int */
			if (OBJ_GET_TYPE(TOS) != OBJ_TYPE_INT) {
				PM_RAISE(retval, PM_RET_EX_TYPE);
				break;
			}

			/* Otherwise perform bit-wise complement */
			retval = int_bitInvert(TOS, &pobj2);
			PM_BREAK_IF_ERROR(retval);
			TOS = pobj2;
			continue;

		case LIST_APPEND:
			/* list_append will raise a TypeError if TOS1 is not a list */
			retval = list_append(TOS1, TOS);
			PM_SP -= 2;
			continue;

		case BINARY_POWER:
		case INPLACE_POWER:

#ifdef HAVE_FLOAT
			if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_FLT)
			    || (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_FLT)) {
				/* Calculate float power */
				retval = float_op(TOS1, TOS, &pobj3, 'P');
				PM_BREAK_IF_ERROR(retval);
				PM_SP--;
				TOS = pobj3;
				continue;
			}
#endif				/* HAVE_FLOAT */

			/* Calculate integer power */
			retval = int_pow(TOS1, TOS, &pobj3);
			PM_BREAK_IF_ERROR(retval);

			/* Set return value */
			PM_SP--;
			TOS = pobj3;
			continue;

		case GET_ITER:
#ifdef HAVE_GENERATORS
			/* Raise TypeError if TOS is an instance, but not iterable */
			if (OBJ_GET_TYPE(TOS) == OBJ_TYPE_CLI) {
				retval =
				    class_getAttr(TOS, PM_NEXT_STR, &pobj1);
				if (retval != PM_RET_OK) {
					PM_RAISE(retval, PM_RET_EX_TYPE);
					break;
				}
			} else
#endif				/* HAVE_GENERATORS */
			{
				/* Convert sequence to sequence-iterator */
				retval = seqiter_new(TOS, &pobj1);
				PM_BREAK_IF_ERROR(retval);

				/* Put sequence-iterator on top of stack */
				TOS = pobj1;
			}
			continue;

		case BINARY_MULTIPLY:
		case INPLACE_MULTIPLY:
			/* If both objs are ints, perform the op */
			if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_INT)
			    && (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_INT)) {
				retval = int_new(((pPmInt_t) TOS1)->val *
						 ((pPmInt_t) TOS)->val, &pobj3);
				PM_BREAK_IF_ERROR(retval);
				PM_SP--;
				TOS = pobj3;
				continue;
			}
#ifdef HAVE_FLOAT
			else if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_FLT)
				 || (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_FLT)) {
				retval = float_op(TOS1, TOS, &pobj3, '*');
				PM_BREAK_IF_ERROR(retval);
				PM_SP--;
				TOS = pobj3;
				continue;
			}
#endif				/* HAVE_FLOAT */

#ifdef HAVE_REPLICATION
			/* If it's a list replication operation */
			else if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_INT)
				 && (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_LST)) {
				t16 = (int16_t) ((pPmInt_t) TOS)->val;
				if (t16 < 0) {
					t16 = 0;
				}

				retval = list_replicate(TOS1, t16, &pobj3);
				PM_BREAK_IF_ERROR(retval);
				PM_SP--;
				TOS = pobj3;
				continue;
			}

			/* If it's a tuple replication operation */
			else if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_INT)
				 && (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_TUP)) {
				t16 = (int16_t) ((pPmInt_t) TOS)->val;
				if (t16 < 0) {
					t16 = 0;
				}

				retval = tuple_replicate(TOS1, t16, &pobj3);
				PM_BREAK_IF_ERROR(retval);
				PM_SP--;
				TOS = pobj3;
				continue;
			}

			/* If it's a string replication operation */
			else if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_INT)
				 && (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_STR)) {
				t16 = (int16_t) ((pPmInt_t) TOS)->val;
				if (t16 < 0) {
					t16 = 0;
				}

				pobj2 = TOS1;
				pobj2 = (pPmObj_t) & ((pPmString_t) pobj2)->val;
				retval = string_replicate((uint8_t const
							   **)(uint8_t *) &
							  pobj2, t16, &pobj3);
				PM_BREAK_IF_ERROR(retval);
				PM_SP--;
				TOS = pobj3;
				continue;
			}
#endif				/* HAVE_REPLICATION */

			/* Otherwise raise a TypeError */
			PM_RAISE(retval, PM_RET_EX_TYPE);
			break;

		case BINARY_DIVIDE:
		case INPLACE_DIVIDE:
		case BINARY_FLOOR_DIVIDE:
		case INPLACE_FLOOR_DIVIDE:

#ifdef HAVE_FLOAT
			if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_FLT)
			    || (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_FLT)) {
				retval = float_op(TOS1, TOS, &pobj3, '/');
				PM_BREAK_IF_ERROR(retval);
				PM_SP--;
				TOS = pobj3;
				continue;
			}
#endif				/* HAVE_FLOAT */

			/* Otherwise perform operation */
			retval = int_divmod(TOS1, TOS, '/', &pobj3);
			PM_BREAK_IF_ERROR(retval);
			PM_SP--;
			TOS = pobj3;
			continue;

		case BINARY_MODULO:
		case INPLACE_MODULO:

#ifdef HAVE_STRING_FORMAT
			/* If it's a string, perform string format */
			if (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_STR) {
				retval =
				    string_format((pPmString_t) TOS1, TOS,
						  &pobj3);
				PM_BREAK_IF_ERROR(retval);
				PM_SP--;
				TOS = pobj3;
				continue;
			}
#endif				/* HAVE_STRING_FORMAT */

#ifdef HAVE_FLOAT
			if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_FLT)
			    || (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_FLT)) {
				retval = float_op(TOS1, TOS, &pobj3, '%');
				PM_BREAK_IF_ERROR(retval);
				PM_SP--;
				TOS = pobj3;
				continue;
			}
#endif				/* HAVE_FLOAT */

			/* Otherwise perform operation */
			retval = int_divmod(TOS1, TOS, '%', &pobj3);
			PM_BREAK_IF_ERROR(retval);
			PM_SP--;
			TOS = pobj3;
			continue;

		case STORE_MAP:
			/* #213: Add support for Python 2.6 bytecodes */
			C_ASSERT(OBJ_GET_TYPE(TOS2) == OBJ_TYPE_DIC);
			retval = dict_setItem(TOS2, TOS, TOS1);
			PM_BREAK_IF_ERROR(retval);
			PM_SP -= 2;
			continue;

		case BINARY_ADD:
		case INPLACE_ADD:

#ifdef HAVE_FLOAT
			if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_FLT)
			    || (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_FLT)) {
				retval = float_op(TOS1, TOS, &pobj3, '+');
				PM_BREAK_IF_ERROR(retval);
				PM_SP--;
				TOS = pobj3;
				continue;
			}
#endif				/* HAVE_FLOAT */

			/* If both objs are ints, perform the op */
			if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_INT)
			    && (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_INT)) {
				retval = int_new(((pPmInt_t) TOS1)->val +
						 ((pPmInt_t) TOS)->val, &pobj3);
				PM_BREAK_IF_ERROR(retval);
				PM_SP--;
				TOS = pobj3;
				continue;
			}

			/* #242: If both objs are strings, perform concatenation */
			if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_STR)
			    && (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_STR)) {
				retval = string_concat((pPmString_t) TOS1,
						       (pPmString_t) TOS,
						       &pobj3);
				PM_BREAK_IF_ERROR(retval);
				PM_SP--;
				TOS = pobj3;
				continue;
			}

			/* Otherwise raise a TypeError */
			PM_RAISE(retval, PM_RET_EX_TYPE);
			break;

		case BINARY_SUBTRACT:
		case INPLACE_SUBTRACT:

#ifdef HAVE_FLOAT
			if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_FLT)
			    || (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_FLT)) {
				retval = float_op(TOS1, TOS, &pobj3, '-');
				PM_BREAK_IF_ERROR(retval);
				PM_SP--;
				TOS = pobj3;
				continue;
			}
#endif				/* HAVE_FLOAT */

			/* If both objs are ints, perform the op */
			if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_INT)
			    && (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_INT)) {
				retval = int_new(((pPmInt_t) TOS1)->val -
						 ((pPmInt_t) TOS)->val, &pobj3);
				PM_BREAK_IF_ERROR(retval);
				PM_SP--;
				TOS = pobj3;
				continue;
			}

			/* Otherwise raise a TypeError */
			PM_RAISE(retval, PM_RET_EX_TYPE);
			break;

		case BINARY_SUBSCR:
			/* Implements TOS = TOS1[TOS]. */

			if (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_DIC) {
				retval = dict_getItem(TOS1, TOS, &pobj3);
			} else {
				/* Raise a TypeError if index is not an Integer or Bool */
				if ((OBJ_GET_TYPE(TOS) != OBJ_TYPE_INT)
				    && (OBJ_GET_TYPE(TOS) != OBJ_TYPE_BOOL)) {
					PM_RAISE(retval, PM_RET_EX_TYPE);
					break;
				}

				pobj1 = TOS1;
#ifdef HAVE_BYTEARRAY
				/* If object is an instance, get the thing it contains */
				if (OBJ_GET_TYPE(pobj1) == OBJ_TYPE_CLI) {
					retval =
					    dict_getItem((pPmObj_t)
							 ((pPmInstance_t)
							  pobj1)->cli_attrs,
							 PM_NONE, &pobj2);
					PM_RETURN_IF_ERROR(retval);
					pobj1 = pobj2;
				}
#endif				/* HAVE_BYTEARRAY */

				/* Ensure the index doesn't overflow */
				C_ASSERT(((pPmInt_t) TOS)->val <= 0x0000FFFF);
				t16 = (int16_t) ((pPmInt_t) TOS)->val;

				retval = seq_getSubscript(pobj1, t16, &pobj3);
			}
			PM_BREAK_IF_ERROR(retval);
			PM_SP--;
			TOS = pobj3;
			continue;

#ifdef HAVE_FLOAT
			/* #213: Add support for Python 2.6 bytecodes */
		case BINARY_TRUE_DIVIDE:
		case INPLACE_TRUE_DIVIDE:

			/* Perform division; float_op() checks for types and zero-div */
			retval = float_op(TOS1, TOS, &pobj3, '/');
			PM_BREAK_IF_ERROR(retval);
			PM_SP--;
			TOS = pobj3;
			continue;
#endif				/* HAVE_FLOAT */

		case SLICE_0:
			/* Implements TOS = TOS[:], push a copy of the sequence */

			/* Create a copy if it is a list */
			if (OBJ_GET_TYPE(TOS) == OBJ_TYPE_LST) {
				retval = list_copy(TOS, &pobj2);
				PM_BREAK_IF_ERROR(retval);

				TOS = pobj2;
			}

			/* If TOS is an immutable sequence leave it (no op) */

			/* Raise a TypeError for types that can not be sliced */
			else if ((OBJ_GET_TYPE(TOS) != OBJ_TYPE_STR)
				 && (OBJ_GET_TYPE(TOS) != OBJ_TYPE_TUP)) {
				PM_RAISE(retval, PM_RET_EX_TYPE);
				break;
			}
			continue;

#ifdef HAVE_SLICE
		case SLICE_1:
		case SLICE_2:
		case SLICE_3:
			{
				pPmObj_t pstart = PM_ZERO;
				pPmObj_t pend = PM_NONE;
				pPmObj_t pstride = PM_ONE;

				switch (bc) {
				case SLICE_1:
					/* Implements TOS = TOS1[TOS:] */
					pstart = TOS;
					pobj1 = TOS1;
					--PM_SP;
					break;

				case SLICE_2:
					/* Implements TOS = TOS1[:TOS] */
					pend = TOS;
					pobj1 = TOS1;
					--PM_SP;
					break;

				case SLICE_3:
					/* Implements TOS = TOS2[TOS1:TOS] */
					pend = TOS;
					pstart = TOS1;
					pobj1 = TOS2;
					PM_SP -= 2;
					break;
				}

				switch (OBJ_GET_TYPE(pobj1)) {
				case OBJ_TYPE_LST:
					retval =
					    list_slice(pobj1, pstart, pend,
						       pstride, &pobj2);
					PM_BREAK_IF_ERROR(retval);
					TOS = pobj2;
					continue;

				case OBJ_TYPE_STR:
					retval =
					    string_slice(pobj1, pstart, pend,
							 pstride, &pobj2);
					PM_BREAK_IF_ERROR(retval);
					TOS = pobj2;
					continue;

				case OBJ_TYPE_TUP:
					retval =
					    tuple_slice(pobj1, pstart, pend,
							pstride, &pobj2);
					PM_BREAK_IF_ERROR(retval);
					TOS = pobj2;
					continue;

				default:
					PM_RAISE(retval, PM_RET_EX_TYPE);
					break;
				}
				break;
			}
#endif				/* HAVE_SLICE */

		case STORE_SUBSCR:
			/* Implements TOS1[TOS] = TOS2 */

			/* If it's a list */
			if (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_LST) {
				/* Ensure subscr is an int or bool */
				if ((OBJ_GET_TYPE(TOS) != OBJ_TYPE_INT)
				    && (OBJ_GET_TYPE(TOS) != OBJ_TYPE_BOOL)) {
					PM_RAISE(retval, PM_RET_EX_TYPE);
					break;
				}

				/* Set the list item */
				retval = list_setItem(TOS1,
						      (int16_t) (((pPmInt_t)
								  TOS)->val),
						      TOS2);
				PM_BREAK_IF_ERROR(retval);
				PM_SP -= 3;
				continue;
			}

			/* If it's a dict */
			if (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_DIC) {
				/* Set the dict item */
				retval = dict_setItem(TOS1, TOS, TOS2);
				PM_BREAK_IF_ERROR(retval);
				PM_SP -= 3;
				continue;
			}
#ifdef HAVE_BYTEARRAY
			/* If object is an instance, get the thing it contains */
			if (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_CLI) {
				retval =
				    dict_getItem((pPmObj_t)
						 ((pPmInstance_t) TOS1)->
						 cli_attrs, PM_NONE, &pobj2);

				/* Raise TypeError if instance isn't a ByteArray */
				if ((retval == PM_RET_EX_KEY)
				    || (OBJ_GET_TYPE(pobj2) != OBJ_TYPE_BYA)) {
					PM_RAISE(retval, PM_RET_EX_TYPE);
					break;
				}
				PM_BREAK_IF_ERROR(retval);

				/* Ensure subscr is an int or bool */
				if ((OBJ_GET_TYPE(TOS) != OBJ_TYPE_INT)
				    && (OBJ_GET_TYPE(TOS) != OBJ_TYPE_BOOL)) {
					PM_RAISE(retval, PM_RET_EX_TYPE);
					break;
				}

				retval = bytearray_setItem(pobj2,
							   (int16_t) (((pPmInt_t) TOS)->val), TOS2);
				PM_BREAK_IF_ERROR(retval);
				PM_SP -= 3;
				continue;
			}
#endif				/* HAVE_BYTEARRAY */

			/* TypeError for all else */
			PM_RAISE(retval, PM_RET_EX_TYPE);
			break;

#ifdef HAVE_DEL
		case DELETE_SUBSCR:

			if ((OBJ_GET_TYPE(TOS1) == OBJ_TYPE_LST)
			    && (OBJ_GET_TYPE(TOS) == OBJ_TYPE_INT)) {
				retval = list_delItem(TOS1,
						      (int16_t) ((pPmInt_t)
								 TOS)->val);
			}

			else if ((OBJ_GET_TYPE(TOS1) == OBJ_TYPE_DIC)
				 && (OBJ_GET_TYPE(TOS) <=
				     OBJ_TYPE_HASHABLE_MAX)) {
				retval = dict_delItem(TOS1, TOS);
			}

			/* Raise TypeError if obj is not a list or dict */
			else {
				PM_RAISE(retval, PM_RET_EX_TYPE);
			}

			PM_BREAK_IF_ERROR(retval);
			PM_SP -= 2;
			continue;
#endif				/* HAVE_DEL */

		case BINARY_LSHIFT:
		case INPLACE_LSHIFT:
			/* If both objs are ints, perform the op */
			if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_INT)
			    && (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_INT)) {
				retval = int_new(((pPmInt_t) TOS1)->val <<
						 ((pPmInt_t) TOS)->val, &pobj3);
				PM_BREAK_IF_ERROR(retval);
				PM_SP--;
				TOS = pobj3;
				continue;
			}

			/* Otherwise raise a TypeError */
			PM_RAISE(retval, PM_RET_EX_TYPE);
			break;

		case BINARY_RSHIFT:
		case INPLACE_RSHIFT:
			/* If both objs are ints, perform the op */
			if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_INT)
			    && (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_INT)) {
				retval = int_new(((pPmInt_t) TOS1)->val >>
						 ((pPmInt_t) TOS)->val, &pobj3);
				PM_BREAK_IF_ERROR(retval);
				PM_SP--;
				TOS = pobj3;
				continue;
			}

			/* Otherwise raise a TypeError */
			PM_RAISE(retval, PM_RET_EX_TYPE);
			break;

		case BINARY_AND:
		case INPLACE_AND:
			/* If both objs are ints, perform the op */
			if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_INT)
			    && (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_INT)) {
				retval = int_new(((pPmInt_t) TOS1)->val &
						 ((pPmInt_t) TOS)->val, &pobj3);
				PM_BREAK_IF_ERROR(retval);
				PM_SP--;
				TOS = pobj3;
				continue;
			}

			/* Otherwise raise a TypeError */
			PM_RAISE(retval, PM_RET_EX_TYPE);
			break;

		case BINARY_XOR:
		case INPLACE_XOR:
			/* If both objs are ints, perform the op */
			if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_INT)
			    && (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_INT)) {
				retval = int_new(((pPmInt_t) TOS1)->val ^
						 ((pPmInt_t) TOS)->val, &pobj3);
				PM_BREAK_IF_ERROR(retval);
				PM_SP--;
				TOS = pobj3;
				continue;
			}

			/* Otherwise raise a TypeError */
			PM_RAISE(retval, PM_RET_EX_TYPE);
			break;

		case BINARY_OR:
		case INPLACE_OR:
			/* If both objs are ints, perform the op */
			if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_INT)
			    && (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_INT)) {
				retval = int_new(((pPmInt_t) TOS1)->val |
						 ((pPmInt_t) TOS)->val, &pobj3);
				PM_BREAK_IF_ERROR(retval);
				PM_SP--;
				TOS = pobj3;
				continue;
			}

			/* Otherwise raise a TypeError */
			PM_RAISE(retval, PM_RET_EX_TYPE);
			break;

#ifdef HAVE_PRINT
		case PRINT_EXPR:
			/* Print interactive expression */
			/* Fallthrough */

		case PRINT_ITEM:
			preempt_disable();
			if (gVmGlobal.needSoftSpace && (bc == PRINT_ITEM)) {
				retval = plat_putByte(' ');
				PM_PREEMPT_ENABLE_BREAK(retval);
			}
			gVmGlobal.needSoftSpace = C_TRUE;

			/* Print out topmost stack element */
			retval =
			    obj_print(TOS, (uint8_t) (bc == PRINT_EXPR),
				      C_FALSE);
			PM_PREEMPT_ENABLE_BREAK(retval);
			PM_SP--;
			if (bc != PRINT_EXPR) {
				preempt_enable_no_resched();
				continue;
			}
			/* If PRINT_EXPR, Fallthrough to print a newline */

		case PRINT_NEWLINE:
			if(bc == PRINT_NEWLINE)
				preempt_disable();

			gVmGlobal.needSoftSpace = C_FALSE;
			if (gVmGlobal.somethingPrinted) {
				retval = plat_putByte('\n');
				gVmGlobal.somethingPrinted = C_FALSE;
			}

			preempt_enable_no_resched();
			PM_BREAK_IF_ERROR(retval);
			continue;
#endif				/* HAVE_PRINT */

		case BREAK_LOOP:
			{
				pPmBlock_t pb1 = PM_FP->fo_blockstack;

				/* Ensure there's a block */
				C_ASSERT(pb1 != C_NULL);

				/* Delete blocks until first loop block */
				while ((pb1->b_type != B_LOOP)
				       && (pb1->next != C_NULL)) {
					pobj2 = (pPmObj_t) pb1;
					pb1 = pb1->next;
					retval = heap_freeChunk(pobj2);
					PM_BREAK_IF_ERROR(retval);
				}

				/* Test again outside while loop */
				PM_BREAK_IF_ERROR(retval);

				/* Restore PM_SP */
				PM_SP = pb1->b_sp;

				/* Goto handler */
				PM_IP = pb1->b_handler;

				/* Pop and delete this block */
				PM_FP->fo_blockstack = pb1->next;
				retval = heap_freeChunk((pPmObj_t) pb1);
				PM_BREAK_IF_ERROR(retval);
			}
			continue;

		case LOAD_LOCALS:
			/* Pushes local attrs dict of current frame */
			/* WARNING: does not copy fo_locals to attrs */
			PM_PUSH((pPmObj_t) PM_FP->fo_attrs);
			continue;

		case RETURN_VALUE:
			/* Get expiring frame's TOS */
			pobj2 = PM_POP();

#if 0 /*__DEBUG__*/
			/* #251: This safety check is disabled because it breaks ipm */
			/* #109: Check that stack should now be empty */
			/* If this is regular frame (not native and not a generator) */
			if ((PM_FP != (pPmFrame_t) (&gVmGlobal.nativeframe)) &&
			    !(PM_FP->fo_func->f_co->co_flags & CO_GENERATOR)) {
				/* An empty stack points one past end of locals */
				t8 = PM_FP->fo_func->f_co->co_nlocals;
				C_ASSERT(PM_SP == &(PM_FP->fo_locals[t8]));
			}
#endif				/* __DEBUG__ */

			/* Keep ref of expiring frame */
			pobj1 = (pPmObj_t) PM_FP;
			C_ASSERT(OBJ_GET_TYPE(pobj1) == OBJ_TYPE_FRM);

			/* If no previous frame, quit thread */
			if (PM_FP->fo_back == C_NULL) {
				gVmGlobal.pthread->interpctrl =
				    INTERP_CTRL_EXIT;
				retval = PM_RET_OK;
				break;
			}

			/* Otherwise return to previous frame */
			PM_FP = PM_FP->fo_back;

#ifdef HAVE_GENERATORS
			/* If returning function was a generator */
			if (((pPmFrame_t) pobj1)->fo_func->f_co->
			    co_flags & CO_GENERATOR) {
				/* Raise a StopIteration exception */
				PM_RAISE(retval, PM_RET_EX_STOP);
				break;
			}
#endif				/* HAVE_GENERATORS */

#ifdef HAVE_CLASSES
			/*
			 * If returning function was class initializer
			 * do not push a return object
			 */
			if (((pPmFrame_t) pobj1)->fo_isInit) {
				/* Raise TypeError if __init__ did not return None */
				if (pobj2 != PM_NONE) {
					PM_RAISE(retval, PM_RET_EX_TYPE);
					break;
				}
			} else
#endif				/* HAVE_CLASSES */

				/*
				 * Push frame's return val, except if the expiring frame
				 * was due to an import statement
				 */
			if (!(((pPmFrame_t) pobj1)->fo_isImport)) {
				PM_PUSH(pobj2);
			}

			/* Deallocate expired frame */
			PM_BREAK_IF_ERROR(heap_freeChunk(pobj1));
			continue;

#ifdef HAVE_IMPORTS
		case IMPORT_STAR:
			/* #102: Implement the remaining IMPORT_ bytecodes */
			/* Expect a module on the top of the stack */
			C_ASSERT(OBJ_GET_TYPE(TOS) == OBJ_TYPE_MOD);

			/* Update PM_FP's attrs with those of the module on the stack */
			retval = dict_update((pPmObj_t) PM_FP->fo_attrs,
					     (pPmObj_t) ((pPmFunc_t) TOS)->
					     f_attrs, C_TRUE);
			PM_BREAK_IF_ERROR(retval);
			PM_SP--;
			continue;
#endif				/* HAVE_IMPORTS */

#ifdef HAVE_GENERATORS
		case YIELD_VALUE:
			/* #207: Add support for the yield keyword */
			/* Get expiring frame's TOS */
			pobj1 = PM_POP();

			/* Raise TypeError if __init__ did not return None */
			/* (Yield means this is a generator) */
			if ((PM_FP)->fo_isInit) {
				PM_RAISE(retval, PM_RET_EX_TYPE);
				break;
			}

			/* Return to previous frame */
			PM_FP = PM_FP->fo_back;

			/* Push yield value onto caller's TOS */
			PM_PUSH(pobj1);
			continue;
#endif				/* HAVE_GENERATORS */

		case POP_BLOCK:
			/* Get ptr to top block */
			pobj1 = (pPmObj_t) PM_FP->fo_blockstack;

			/* If there's no block, raise SystemError */
			C_ASSERT(pobj1 != C_NULL);

			/* Pop block */
			PM_FP->fo_blockstack = PM_FP->fo_blockstack->next;

			/* Set stack to previous level, jump to code outside block */
			PM_SP = ((pPmBlock_t) pobj1)->b_sp;
			PM_IP = ((pPmBlock_t) pobj1)->b_handler;

			PM_BREAK_IF_ERROR(heap_freeChunk(pobj1));
			continue;

#ifdef HAVE_CLASSES
		case BUILD_CLASS:
			/* Create and push new class */
			retval = class_new(TOS, TOS1, TOS2, &pobj2);
			PM_BREAK_IF_ERROR(retval);
			PM_SP -= 2;
			TOS = pobj2;
			continue;
#endif				/* HAVE_CLASSES */

	    /***************************************************
             * All bytecodes after 90 (0x5A) have a 2-byte arg
             * that needs to be swallowed using GET_ARG().
             **************************************************/

		case STORE_NAME:
			/* Get name index */
			t16 = GET_ARG();

			/* Get key */
			pobj2 = PM_FP->fo_func->f_co->co_names->val[t16];

			/* Set key=val in current frame's attrs dict */
			retval =
			    dict_setItem((pPmObj_t) PM_FP->fo_attrs, pobj2,
					 TOS);
			PM_BREAK_IF_ERROR(retval);
			PM_SP--;
			continue;

#ifdef HAVE_DEL
		case DELETE_NAME:
			/* Get name index */
			t16 = GET_ARG();

			/* Get key */
			pobj2 = PM_FP->fo_func->f_co->co_names->val[t16];

			/* Remove key,val pair from current frame's attrs dict */
			retval =
			    dict_delItem((pPmObj_t) PM_FP->fo_attrs, pobj2);
			PM_BREAK_IF_ERROR(retval);
			continue;
#endif				/* HAVE_DEL */

		case UNPACK_SEQUENCE:
			/* Get ptr to sequence */
			pobj1 = PM_POP();

#ifdef HAVE_BYTEARRAY
			/* If object is an instance, get the thing it contains */
			if (OBJ_GET_TYPE(pobj1) == OBJ_TYPE_CLI) {
				retval =
				    dict_getItem((pPmObj_t)
						 ((pPmInstance_t) pobj1)->
						 cli_attrs, PM_NONE, &pobj2);
				PM_RETURN_IF_ERROR(retval);
				pobj1 = pobj2;
			}
#endif				/* HAVE_BYTEARRAY */

			/*
			 * Get the length of the sequence; this will
			 * raise TypeError if obj is not a sequence.
			 *
			 * #59: Unpacking to a Dict shall not be supported
			 */
			retval = seq_getLength(pobj1, (uint16_t *) & t16);
			if (retval != PM_RET_OK) {
				GET_ARG();
				break;
			}

			/* Raise ValueError if seq length does not match num args */
			if (t16 != GET_ARG()) {
				PM_RAISE(retval, PM_RET_EX_VAL);
				break;
			}

			/* Push sequence's objs onto stack */
			for (; --t16 >= 0;) {
				retval = seq_getSubscript(pobj1, t16, &pobj2);
				PM_BREAK_IF_ERROR(retval);
				PM_PUSH(pobj2);
			}

			/* Test again outside the for loop */
			PM_BREAK_IF_ERROR(retval);
			continue;

		case FOR_ITER:
			t16 = GET_ARG();

#ifdef HAVE_GENERATORS
			/* If TOS is an instance, call next method */
			if (OBJ_GET_TYPE(TOS) == OBJ_TYPE_CLI) {
				/* Get the next() func */
				retval =
				    class_getAttr(TOS, PM_NEXT_STR, &pobj1);
				PM_BREAK_IF_ERROR(retval);

				/* Push the func and instance as an arg */
				pobj2 = TOS;
				PM_PUSH(pobj1);
				PM_PUSH(pobj2);
				t16 = 1;

				/* Ensure pobj1 is the func */
				goto CALL_FUNC_FOR_ITER;
			} else
#endif				/* HAVE_GENERATORS */
			{
				/* Get the next item in the sequence iterator */
				retval = seqiter_getNext(TOS, &pobj2);
			}

			/* Catch StopIteration early: pop iterator and break loop */
			if (retval == PM_RET_EX_STOP) {
				PM_SP--;
				retval = PM_RET_OK;
				PM_IP += t16;
				continue;
			}
			PM_BREAK_IF_ERROR(retval);

			/* Push the next item onto the stack */
			PM_PUSH(pobj2);
			continue;

		case STORE_ATTR:
			/* TOS.name = TOS1 */
			/* Get names index */
			t16 = GET_ARG();

			/* Get attrs dict from obj */
			if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_FXN)
			    || (OBJ_GET_TYPE(TOS) == OBJ_TYPE_MOD)) {
				pobj2 = (pPmObj_t) ((pPmFunc_t) TOS)->f_attrs;
			}
#ifdef HAVE_CLASSES
			else if (OBJ_GET_TYPE(TOS) == OBJ_TYPE_CLO) {
				pobj2 = (pPmObj_t) ((pPmClass_t) TOS)->cl_attrs;
			} else if (OBJ_GET_TYPE(TOS) == OBJ_TYPE_CLI) {
				pobj2 =
				    (pPmObj_t) ((pPmInstance_t) TOS)->cli_attrs;
			} else if (OBJ_GET_TYPE(TOS) == OBJ_TYPE_MTH) {
				pobj2 = (pPmObj_t) ((pPmMethod_t) TOS)->m_attrs;
			}
#endif				/* HAVE_CLASSES */

			/* Other types result in an AttributeError */
			else {
				PM_RAISE(retval, PM_RET_EX_ATTR);
				break;
			}

			/* If attrs is not a dict, raise SystemError */
			if (OBJ_GET_TYPE(pobj2) != OBJ_TYPE_DIC) {
				PM_RAISE(retval, PM_RET_EX_SYS);
				break;
			}

			/* Get name/key obj */
			pobj3 = PM_FP->fo_func->f_co->co_names->val[t16];

			/* Set key=val in obj's dict */
			retval = dict_setItem(pobj2, pobj3, TOS1);
			PM_BREAK_IF_ERROR(retval);
			PM_SP -= 2;
			continue;

#ifdef HAVE_DEL
		case DELETE_ATTR:
			/* del TOS.name */
			/* Get names index */
			t16 = GET_ARG();

			/* Get attrs dict from obj */
			if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_FXN)
			    || (OBJ_GET_TYPE(TOS) == OBJ_TYPE_MOD)) {
				pobj2 = (pPmObj_t) ((pPmFunc_t) TOS)->f_attrs;
			}
#ifdef HAVE_CLASSES
			else if (OBJ_GET_TYPE(TOS) == OBJ_TYPE_CLO) {
				pobj2 = (pPmObj_t) ((pPmClass_t) TOS)->cl_attrs;
			} else if (OBJ_GET_TYPE(TOS) == OBJ_TYPE_CLI) {
				pobj2 =
				    (pPmObj_t) ((pPmInstance_t) TOS)->cli_attrs;
			} else if (OBJ_GET_TYPE(TOS) == OBJ_TYPE_MTH) {
				pobj2 = (pPmObj_t) ((pPmMethod_t) TOS)->m_attrs;
			}
#endif				/* HAVE_CLASSES */

			/* Other types result in an AttributeError */
			else {
				PM_RAISE(retval, PM_RET_EX_ATTR);
				break;
			}

			/* If attrs is not a dict, raise SystemError */
			if (OBJ_GET_TYPE(pobj2) != OBJ_TYPE_DIC) {
				PM_RAISE(retval, PM_RET_EX_SYS);
				break;
			}

			/* Get name/key obj */
			pobj3 = PM_FP->fo_func->f_co->co_names->val[t16];

			/* Remove key,val from obj's dict */
			retval = dict_delItem(pobj2, pobj3);

			/* Raise an AttributeError if key is not found */
			if (retval == PM_RET_EX_KEY) {
				PM_RAISE(retval, PM_RET_EX_ATTR);
			}

			PM_BREAK_IF_ERROR(retval);
			PM_SP--;
			continue;
#endif				/* HAVE_DEL */

		case STORE_GLOBAL:
			/* Get name index */
			t16 = GET_ARG();

			/* Get key */
			pobj2 = PM_FP->fo_func->f_co->co_names->val[t16];

			/* Set key=val in global dict */
			retval =
			    dict_setItem((pPmObj_t) PM_FP->fo_globals, pobj2,
					 TOS);
			PM_BREAK_IF_ERROR(retval);
			PM_SP--;
			continue;

#ifdef HAVE_DEL
		case DELETE_GLOBAL:
			/* Get name index */
			t16 = GET_ARG();

			/* Get key */
			pobj2 = PM_FP->fo_func->f_co->co_names->val[t16];

			/* Remove key,val from globals */
			retval =
			    dict_delItem((pPmObj_t) PM_FP->fo_globals, pobj2);
			PM_BREAK_IF_ERROR(retval);
			continue;
#endif				/* HAVE_DEL */

		case DUP_TOPX:
			t16 = GET_ARG();
			C_ASSERT(t16 <= 3);

			pobj1 = TOS;
			pobj2 = TOS1;
			pobj3 = TOS2;
			if (t16 >= 3)
				PM_PUSH(pobj3);
			if (t16 >= 2)
				PM_PUSH(pobj2);
			if (t16 >= 1)
				PM_PUSH(pobj1);
			continue;

		case LOAD_CONST:
			/* Get const's index in CO */
			t16 = GET_ARG();

			/* Push const on stack */
			PM_PUSH(PM_FP->fo_func->f_co->co_consts->val[t16]);
			continue;

		case LOAD_NAME:
			/* Get name index */
			t16 = GET_ARG();

			/* Get name from names tuple */
			pobj1 = PM_FP->fo_func->f_co->co_names->val[t16];

			/* Get value from frame's attrs dict */
			retval =
			    dict_getItem((pPmObj_t) PM_FP->fo_attrs, pobj1,
					 &pobj2);
			if (retval == PM_RET_EX_KEY) {
				/* Get val from globals */
				retval =
				    dict_getItem((pPmObj_t) PM_FP->fo_globals,
						 pobj1, &pobj2);

				/* Check for name in the builtins module if it is loaded */
				if ((retval == PM_RET_EX_KEY)
				    && (PM_PBUILTINS != C_NULL)) {
					/* Get val from builtins */
					retval =
					    dict_getItem(PM_PBUILTINS, pobj1,
							 &pobj2);
					if (retval == PM_RET_EX_KEY) {
						/* Name not defined, raise NameError */
						PM_RAISE(retval,
							 PM_RET_EX_NAME);
						break;
					}
				}
			}
			PM_BREAK_IF_ERROR(retval);
			PM_PUSH(pobj2);
			continue;

		case BUILD_TUPLE:
			/* Get num items */
			t16 = GET_ARG();
			retval = tuple_new(t16, &pobj1);
			PM_BREAK_IF_ERROR(retval);

			/* Fill tuple with ptrs to objs */
			for (; --t16 >= 0;) {
				((pPmTuple_t) pobj1)->val[t16] = PM_POP();
			}
			PM_PUSH(pobj1);
			continue;

		case BUILD_LIST:
			t16 = GET_ARG();
			retval = list_new(&pobj1);
			PM_BREAK_IF_ERROR(retval);
			for (; --t16 >= 0;) {
				/* Insert obj into list */
				heap_gcPushTempRoot(pobj1, &objid);
				retval = list_insert(pobj1, 0, TOS);
				heap_gcPopTempRoot(objid);
				PM_BREAK_IF_ERROR(retval);
				PM_SP--;
			}
			/* Test again outside for loop */
			PM_BREAK_IF_ERROR(retval);

			/* push list onto stack */
			PM_PUSH(pobj1);
			continue;

		case BUILD_MAP:
			/* Argument is ignored */
			t16 = GET_ARG();
			retval = dict_new(&pobj1);
			PM_BREAK_IF_ERROR(retval);
			PM_PUSH(pobj1);
			continue;

		case LOAD_ATTR:
			/* Implements TOS.attr */
			t16 = GET_ARG();

#ifdef HAVE_AUTOBOX
			/* Autobox the object, if necessary */
			retval = class_autobox(&TOS);
			PM_BREAK_IF_ERROR(retval);
#endif

			/* Get attrs dict from obj */
			if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_FXN) ||
			    (OBJ_GET_TYPE(TOS) == OBJ_TYPE_MOD)) {
				pobj1 = (pPmObj_t) ((pPmFunc_t) TOS)->f_attrs;
			}
#ifdef HAVE_CLASSES
			else if (OBJ_GET_TYPE(TOS) == OBJ_TYPE_CLO) {
				pobj1 = (pPmObj_t) ((pPmClass_t) TOS)->cl_attrs;
			} else if (OBJ_GET_TYPE(TOS) == OBJ_TYPE_CLI) {
				pobj1 =
				    (pPmObj_t) ((pPmInstance_t) TOS)->cli_attrs;
			} else if (OBJ_GET_TYPE(TOS) == OBJ_TYPE_MTH) {
				pobj1 = (pPmObj_t) ((pPmMethod_t) TOS)->m_attrs;
			}
#endif				/* HAVE_CLASSES */

			/* Other types result in an AttributeError */
			else {
				PM_RAISE(retval, PM_RET_EX_ATTR);
				break;
			}

			/* If attrs is not a dict, raise SystemError */
			if (OBJ_GET_TYPE(pobj1) != OBJ_TYPE_DIC) {
				PM_RAISE(retval, PM_RET_EX_SYS);
				break;
			}

			/* Get name */
			pobj2 = PM_FP->fo_func->f_co->co_names->val[t16];

			/* Get attr with given name */
			retval = dict_getItem(pobj1, pobj2, &pobj3);

#ifdef HAVE_CLASSES
			/*
			 * If attr is not found and object is a class or instance,
			 * try to get the attribute from the class attrs or parent(s)
			 */
			if ((retval == PM_RET_EX_KEY) &&
			    ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_CLO)
			     || (OBJ_GET_TYPE(TOS) == OBJ_TYPE_CLI))) {
				retval = class_getAttr(TOS, pobj2, &pobj3);
			}
#endif				/* HAVE_CLASSES */

			/* Raise an AttributeError if key is not found */
			if (retval == PM_RET_EX_KEY) {
				PM_RAISE(retval, PM_RET_EX_ATTR);
			}
			PM_BREAK_IF_ERROR(retval);

#ifdef HAVE_CLASSES
			/* If obj is an instance and attr is a func, create method */
			if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_CLI) &&
			    (OBJ_GET_TYPE(pobj3) == OBJ_TYPE_FXN)) {
				pobj2 = pobj3;
				retval = class_method(TOS, pobj2, &pobj3);
				PM_BREAK_IF_ERROR(retval);
			}
#endif				/* HAVE_CLASSES */

			/* Put attr on the stack */
			TOS = pobj3;
			continue;

		case COMPARE_OP:
			retval = PM_RET_OK;
			t16 = GET_ARG();

#ifdef HAVE_FLOAT
			if ((OBJ_GET_TYPE(TOS) == OBJ_TYPE_FLT)
			    || (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_FLT)) {
				retval =
				    float_compare(TOS1, TOS, &pobj3,
						  (PmCompare_t) t16);
				PM_SP--;
				TOS = pobj3;
				continue;
			}
#endif				/* HAVE_FLOAT */

			/* Handle all integer-to-integer (or bool) comparisons */
			if (((OBJ_GET_TYPE(TOS) == OBJ_TYPE_INT)
			     || (OBJ_GET_TYPE(TOS) == OBJ_TYPE_BOOL))
			    && ((OBJ_GET_TYPE(TOS1) == OBJ_TYPE_INT)
				|| (OBJ_GET_TYPE(TOS1) == OBJ_TYPE_BOOL))) {
				int32_t a = ((pPmInt_t) TOS1)->val;
				int32_t b = ((pPmInt_t) TOS)->val;

				switch (t16) {
                        /* *INDENT-OFF* */
                        case COMP_LT: t8 = (int8_t)(a <  b); break;
                        case COMP_LE: t8 = (int8_t)(a <= b); break;
                        case COMP_EQ: t8 = (int8_t)(a == b); break;
                        case COMP_NE: t8 = (int8_t)(a != b); break;
                        case COMP_GT: t8 = (int8_t)(a >  b); break;
                        case COMP_GE: t8 = (int8_t)(a >= b); break;
                        case COMP_IS: t8 = (int8_t)(TOS == TOS1); break;
                        case COMP_IS_NOT: t8 = (int8_t)(TOS != TOS1);break;
                        case COMP_IN:
                        case COMP_NOT_IN:
                            PM_RAISE(retval, PM_RET_EX_TYPE);
                            break;

                        default:
                            /* Other compares are not yet supported */
                            PM_RAISE(retval, PM_RET_EX_SYS);
                            break;
                        /* *INDENT-ON* */
				}
				PM_BREAK_IF_ERROR(retval);
				pobj3 = (t8) ? PM_TRUE : PM_FALSE;
			}

			/* Handle non-integer comparisons */
			else {
				retval = PM_RET_OK;
				switch (t16) {
				case COMP_EQ:
				case COMP_NE:
					/* Handle equality for non-int types */
					pobj3 = PM_FALSE;
					t8 = obj_compare(TOS, TOS1);
					if (((t8 == C_SAME) && (t16 == COMP_EQ))
					    || ((t8 == C_DIFFER)
						&& (t16 == COMP_NE))) {
						pobj3 = PM_TRUE;
					}
					break;

				case COMP_IN:
				case COMP_NOT_IN:
					/* Handle membership comparisons */
					pobj3 = PM_FALSE;
					retval = obj_isIn(TOS, TOS1);
					if (retval == PM_RET_OK) {
						if (t16 == COMP_IN) {
							pobj3 = PM_TRUE;
						}
					} else if (retval == PM_RET_NO) {
						retval = PM_RET_OK;
						if (t16 == COMP_NOT_IN) {
							pobj3 = PM_TRUE;
						}
					}
					break;

				case COMP_IS:
					pobj3 =
					    (TOS == TOS1) ? PM_TRUE : PM_FALSE;
					break;

				case COMP_IS_NOT:
					pobj3 =
					    (TOS != TOS1) ? PM_TRUE : PM_FALSE;
					break;

				default:
					/* Other comparisons are not implemented */
					PM_RAISE(retval, PM_RET_EX_SYS);
					break;
				}
				PM_BREAK_IF_ERROR(retval);
			}
			PM_SP--;
			TOS = pobj3;
			continue;

		case IMPORT_NAME:
			/* Get name index */
			t16 = GET_ARG();

			/* Get name String obj */
			pobj1 = PM_FP->fo_func->f_co->co_names->val[t16];

			/* Pop unused None object */
			PM_SP--;

			/* Ensure "level" is -1; no support for relative import yet */
			C_ASSERT(obj_compare(TOS, PM_NEGONE) == C_SAME);

			/* #178: Fix import so modules are reused */
			/* Return the module if found in the modules dict (cache) */
			retval = dict_getItem(PM_PBUILTINS, PM_MD_STR, &pobj3);
			PM_BREAK_IF_ERROR(retval);
			retval = dict_getItem(pobj3, pobj1, &pobj2);
			if (retval == PM_RET_OK) {
				TOS = pobj2;
				continue;
			}
			if (retval != PM_RET_EX_KEY) {
				break;
			}

			/* Load module from image */
			retval = mod_import(pobj1, &pobj2);
			PM_BREAK_IF_ERROR(retval);

			/* #178: Fix import so modules are reused */
			/*
			 * Store the module's attrs/globals under the module's name
			 * in the global module dict (cache)
			 */
			heap_gcPushTempRoot(pobj2, &objid);
			retval = dict_setItem(pobj3, pobj1, pobj2);
			heap_gcPopTempRoot(objid);
			PM_BREAK_IF_ERROR(retval);

			/* Put Module on top of stack */
			TOS = pobj2;

			/* Code after here is a duplicate of CALL_FUNCTION */
			/* Make frame object to interpret the module's root code */
			heap_gcPushTempRoot(pobj2, &objid);
			retval = frame_new(pobj2, &pobj3);
			heap_gcPopTempRoot(objid);
			PM_BREAK_IF_ERROR(retval);

			/* No arguments to pass */

			/* Keep ref to current frame */
			((pPmFrame_t) pobj3)->fo_back = PM_FP;

			/* Handle to have None popped on return */
			((pPmFrame_t) pobj3)->fo_isImport = (uint8_t) 1;

			/* Set new frame */
			PM_FP = (pPmFrame_t) pobj3;
			continue;

#ifdef HAVE_IMPORTS
		case IMPORT_FROM:
			/* #102: Implement the remaining IMPORT_ bytecodes */
			/* Expect the module on the top of the stack */
			C_ASSERT(OBJ_GET_TYPE(TOS) == OBJ_TYPE_MOD);
			pobj1 = TOS;

			/* Get the name of the object to import */
			t16 = GET_ARG();
			pobj2 = PM_FP->fo_func->f_co->co_names->val[t16];

			/* Get the object from the module's attributes */
			retval =
			    dict_getItem((pPmObj_t) ((pPmFunc_t) pobj1)->
					 f_attrs, pobj2, &pobj3);
			PM_BREAK_IF_ERROR(retval);

			/* Push the object onto the top of the stack */
			PM_PUSH(pobj3);
			continue;
#endif				/* HAVE_IMPORTS */

		case JUMP_FORWARD:
			t16 = GET_ARG();
			PM_IP += t16;
			continue;

		case JUMP_IF_FALSE:
			t16 = GET_ARG();
			if (obj_isFalse(TOS)) {
				PM_IP += t16;
			}
			continue;

		case JUMP_IF_TRUE:
			t16 = GET_ARG();
			if (!obj_isFalse(TOS)) {
				PM_IP += t16;
			}
			continue;

		case JUMP_ABSOLUTE:
		case CONTINUE_LOOP:
			/* Get target offset (bytes) */
			t16 = GET_ARG();

			/* Jump to base_ip + arg */
			PM_IP = PM_FP->fo_func->f_co->co_codeaddr + t16;
			continue;

		case LOAD_GLOBAL:
			/* Get name */
			t16 = GET_ARG();
			pobj1 = PM_FP->fo_func->f_co->co_names->val[t16];

			/* Try globals first */
			retval = dict_getItem((pPmObj_t) PM_FP->fo_globals,
					      pobj1, &pobj2);

			/* If that didn't work, try builtins */
			if (retval == PM_RET_EX_KEY) {
				retval =
				    dict_getItem(PM_PBUILTINS, pobj1, &pobj2);

				/* No such global, raise NameError */
				if (retval == PM_RET_EX_KEY) {
					PM_RAISE(retval, PM_RET_EX_NAME);
					break;
				}
			}
			PM_BREAK_IF_ERROR(retval);
			PM_PUSH(pobj2);
			continue;

		case SETUP_LOOP:
			{
				uint8_t *pchunk;

				/* Get block span (bytes) */
				t16 = GET_ARG();

				/* Create block */
				retval =
				    heap_getChunk(sizeof(PmBlock_t), &pchunk);
				PM_BREAK_IF_ERROR(retval);
				pobj1 = (pPmObj_t) pchunk;
				OBJ_SET_TYPE(pobj1, OBJ_TYPE_BLK);

				/* Store current stack pointer */
				((pPmBlock_t) pobj1)->b_sp = PM_SP;

				/* Default handler is to exit block/loop */
				((pPmBlock_t) pobj1)->b_handler = PM_IP + t16;
				((pPmBlock_t) pobj1)->b_type = B_LOOP;

				/* Insert block into blockstack */
				((pPmBlock_t) pobj1)->next =
				    PM_FP->fo_blockstack;
				PM_FP->fo_blockstack = (pPmBlock_t) pobj1;
				continue;
			}

		case LOAD_FAST:
			t16 = GET_ARG();
			PM_PUSH(PM_FP->fo_locals[t16]);
			continue;

		case STORE_FAST:
			t16 = GET_ARG();
			PM_FP->fo_locals[t16] = PM_POP();
			continue;

#ifdef HAVE_DEL
		case DELETE_FAST:
			t16 = GET_ARG();
			PM_FP->fo_locals[t16] = PM_NONE;
			continue;
#endif				/* HAVE_DEL */

#ifdef HAVE_ASSERT
		case RAISE_VARARGS:
			t16 = GET_ARG();

			/* Only supports taking 1 arg for now */
			if (t16 != 1) {
				PM_RAISE(retval, PM_RET_EX_SYS);
				break;
			}

			/* Load Exception class from builtins */
			retval =
			    dict_getItem(PM_PBUILTINS, PM_EXCEPTION_STR,
					 &pobj2);
			if (retval != PM_RET_OK) {
				PM_RAISE(retval, PM_RET_EX_SYS);
				break;
			}

			/* Raise TypeError if TOS is not an instance of Exception */
			pobj1 = TOS;
			if ((OBJ_GET_TYPE(pobj1) != OBJ_TYPE_CLO)
			    || !class_isSubclass(pobj1, pobj2)) {
				PM_RAISE(retval, PM_RET_EX_TYPE);
				break;
			}

			/* Push the traceback, parameter and exception object */
			TOS = PM_NONE;
			PM_PUSH(PM_NONE);
			PM_PUSH(pobj1);

			/* Get the exception's code attr */
			retval =
			    dict_getItem((pPmObj_t) ((pPmClass_t) pobj1)->
					 cl_attrs, PM_CODE_STR, &pobj2);
			PM_BREAK_IF_ERROR(retval);

			/* Raise exception by breaking with retval set to code */
			PM_RAISE(retval,
				 (PmReturn_t) (((pPmInt_t) pobj2)->val & 0xFF));
			break;
#endif				/* HAVE_ASSERT */

		case CALL_FUNCTION:
			/* Get num args */
			t16 = GET_ARG();

			/* Ensure no keyword args */
			if ((t16 & (uint16_t) 0xFF00) != 0) {
				PM_RAISE(retval, PM_RET_EX_SYS);
				break;
			}

			/* Get the callable */
			pobj1 = STACK(t16);

			/* Useless push to get temp-roots stack level used in cleanup */
			heap_gcPushTempRoot(pobj1, &objid);

			C_DEBUG_PRINT(VERBOSITY_LOW,
				      "interpret(), CALL_FUNCTION on <obj type=%d @ %p>\n",
				      OBJ_GET_TYPE(pobj1), pobj1);

#ifdef HAVE_GENERATORS
			/* If the callable is a generator function (can't be native) */
			if ((OBJ_GET_TYPE(pobj1) == OBJ_TYPE_FXN)
			    && (OBJ_GET_TYPE(((pPmFunc_t) pobj1)->f_co) ==
				OBJ_TYPE_COB)
			    && (((pPmFunc_t) pobj1)->f_co->
				co_flags & CO_GENERATOR)) {
#ifdef HAVE_DEFAULTARGS
				/* Num required args := argcount - num default args */
				t8 = ((pPmFunc_t) pobj1)->f_co->co_argcount;
				if (((pPmFunc_t) pobj1)->f_defaultargs !=
				    C_NULL) {
					t8 -=
					    ((pPmTuple_t) ((pPmFunc_t) pobj1)->
					     f_defaultargs)->length;
				}

				/*
				 * Raise a TypeError if num args passed
				 * is more than allowed or less than required
				 */
				if (((t16 & ((uint8_t) 0xFF))
				     > ((pPmFunc_t) pobj1)->f_co->co_argcount)
				    || ((t16 & ((uint8_t) 0xFF)) < t8))
#else
				if ((t16 & ((uint8_t) 0xFF)) !=
				    ((pPmFunc_t) pobj1)->f_co->co_argcount)
#endif				/* HAVE_DEFAULTARGS */
				{
					PM_RAISE(retval, PM_RET_EX_TYPE);
					break;
				}

				/* Collect the function and arguments into a tuple */
				retval = tuple_new(t16 + 1, &pobj2);
				heap_gcPushTempRoot(pobj2, &objid2);
				PM_GOTO_IF_ERROR(retval, CALL_FUNC_CLEANUP);
				sli_memcpy((uint8_t *) & ((pPmTuple_t) pobj2)->
					   val, (uint8_t *) & STACK(t16),
					   (t16 + 1) * sizeof(pPmObj_t));

				/* Remove old args, push func/args tuple as one arg */
				PM_SP -= t16;
				PM_PUSH(pobj2);
				t16 = 1;

				/* Set pobj1 and stack to create an instance of Generator */
				retval =
				    dict_getItem(PM_PBUILTINS, PM_GENERATOR_STR,
						 &pobj1);
				C_ASSERT(retval == PM_RET_OK);
				STACK(t16) = pobj1;
			}
#endif				/* HAVE_GENERATORS */

#ifdef HAVE_CLASSES
			/* If the callable is a class, create an instance of it */
			if (OBJ_GET_TYPE(pobj1) == OBJ_TYPE_CLO) {
				/* This marks that the original callable was a class */
				bc = 0;

				/* Replace class with new instance */
				retval = class_instantiate(pobj1, &pobj2);
				heap_gcPushTempRoot(pobj2, &objid2);
				STACK(t16) = pobj2;

				/* If __init__ does not exist */
				pobj3 = C_NULL;
				retval =
				    class_getAttr(pobj1, PM_INIT_STR, &pobj3);
				if (retval == PM_RET_EX_KEY) {
					/* Raise TypeError if there are args */
					if (t16 > 0) {
						PM_RAISE(retval,
							 PM_RET_EX_TYPE);
						goto CALL_FUNC_CLEANUP;
					}

					/* Otherwise, continue with instance */
					heap_gcPopTempRoot(objid);
					continue;
				} else if (retval != PM_RET_OK) {
					PM_GOTO_IF_ERROR(retval,
							 CALL_FUNC_CLEANUP);
				}

				/* Slide the arguments up 1 slot in the stack */
				PM_SP++;
				for (t8 = 0; t8 < t16; t8++) {
					STACK(t8) = STACK(t8 + 1);
				}

				/* Convert __init__ to method, insert it as the callable */
				retval = class_method(pobj2, pobj3, &pobj1);
				PM_GOTO_IF_ERROR(retval, CALL_FUNC_CLEANUP);
				heap_gcPushTempRoot(pobj2, &objid2);
				STACK(t16) = pobj1;
				/* Fall through to call the method */
			}

			if (OBJ_GET_TYPE(pobj1) == OBJ_TYPE_MTH) {
				/* Set the method's func to be the callable */
				STACK(t16) =
				    (pPmObj_t) ((pPmMethod_t) pobj1)->m_func;

				/* Slide the arguments up 1 slot in the stack */
				PM_SP++;
				for (t8 = 0; t8 < t16; t8++) {
					STACK(t8) = STACK(t8 + 1);
				}

				/* Insert instance as "self" arg to the method */
				STACK(t16++) =
				    (pPmObj_t) ((pPmMethod_t) pobj1)->
				    m_instance;

				/* Refresh the callable */
				pobj1 =
				    (pPmObj_t) ((pPmMethod_t) pobj1)->m_func;
			}
#endif				/* HAVE_CLASSES */

#ifdef HAVE_GENERATORS
 CALL_FUNC_FOR_ITER:
#endif				/* HAVE_GENERATORS */
			/* Raise a TypeError if object is not callable */
			if (OBJ_GET_TYPE(pobj1) != OBJ_TYPE_FXN) {
				PM_RAISE(retval, PM_RET_EX_TYPE);
				goto CALL_FUNC_CLEANUP;
			}

			/* If it is a regular func (not native) */
			if (OBJ_GET_TYPE(((pPmFunc_t) pobj1)->f_co) ==
			    OBJ_TYPE_COB) {
				/*
				 * #132 Raise TypeError if num args does not match the
				 * code object's expected argcount
				 */

#ifdef HAVE_DEFAULTARGS
				/* Num required args := argcount - num default args */
				t8 = ((pPmFunc_t) pobj1)->f_co->co_argcount;
				if (((pPmFunc_t) pobj1)->f_defaultargs !=
				    C_NULL) {
					t8 -=
					    ((pPmTuple_t) ((pPmFunc_t) pobj1)->
					     f_defaultargs)->length;
				}

				/*
				 * Raise a TypeError if num args passed
				 * is more than allowed or less than required
				 */
				if (((t16 & ((uint8_t) 0xFF))
				     > ((pPmFunc_t) pobj1)->f_co->co_argcount)
				    || ((t16 & ((uint8_t) 0xFF)) < t8))
#else
				if ((t16 & ((uint8_t) 0xFF)) !=
				    ((pPmFunc_t) pobj1)->f_co->co_argcount)
#endif				/* HAVE_DEFAULTARGS */
				{
					PM_RAISE(retval, PM_RET_EX_TYPE);
					break;
				}

				/* Make frame object to run the func object */
				retval = frame_new(pobj1, &pobj2);
				heap_gcPushTempRoot(pobj2, &objid2);
				PM_GOTO_IF_ERROR(retval, CALL_FUNC_CLEANUP);

#ifdef HAVE_CLASSES
				/*
				 * If the original callable was a class, indicate that
				 * the frame is running the initializer so that
				 * its return object is checked for None and ignored.
				 */
				if (bc == 0) {
					((pPmFrame_t) pobj2)->fo_isInit =
					    C_TRUE;
				}
#endif				/* HAVE_CLASSES */

#ifdef HAVE_DEFAULTARGS
				/* If this func has default arguments, put them in place */
				if (((pPmFunc_t) pobj1)->f_defaultargs !=
				    C_NULL) {
					i = 0;

					/* Copy default args into the new frame's locals */
					for ( /* t8 set above */ ;
					     t8 <
					     ((pPmFunc_t) pobj1)->f_co->
					     co_argcount; t8++) {
						((pPmFrame_t) pobj2)->
						    fo_locals[t8] =
						    ((pPmTuple_t)
						     ((pPmFunc_t)
						      pobj1)->f_defaultargs)->
						    val[i++];
					}
				}
#endif				/* HAVE_DEFAULTARGS */

				/* Pass args to new frame */
				while (--t16 >= 0) {
					/*
					 * Pop args from stack right to left,
					 * since args are pushed left to right,
					 */
					((pPmFrame_t) pobj2)->fo_locals[t16] =
					    PM_POP();
				}

#ifdef HAVE_CLOSURES
				/* #256: Add support for closures */
				/* Copy arguments that become cellvars */
				if (((pPmFunc_t) pobj1)->f_co->co_cellvars !=
				    C_NULL) {
					for (t8 = 0;
					     t8 <
					     ((pPmFunc_t) pobj1)->f_co->
					     co_cellvars->length; t8++) {
						if (((pPmInt_t)
						     ((pPmFunc_t) pobj1)->f_co->
						     co_cellvars->val[t8])->
						    val >= 0) {
							((pPmFrame_t) pobj2)->
							    fo_locals[((pPmFunc_t) pobj1)->f_co->co_nlocals + t8] = ((pPmFrame_t) pobj2)->fo_locals[((pPmInt_t) (((pPmFunc_t) pobj1)->f_co->co_cellvars->val[t8]))->val];
						}
					}
				}

				/* Fill frame's freevars with references from closure */
				for (t8 = 0;
				     t8 <
				     ((pPmFunc_t) pobj1)->f_co->co_nfreevars;
				     t8++) {
					C_ASSERT(((pPmFunc_t) pobj1)->
						 f_closure != C_NULL);
					((pPmFrame_t) pobj2)->
					    fo_locals[((pPmFunc_t) pobj1)->
						      f_co->co_nlocals +
						      ((((pPmFunc_t) pobj1)->
							f_co->co_cellvars ==
							C_NULL) ? 0
						       : ((pPmFunc_t) pobj1)->
						       f_co->co_cellvars->
						       length)
						      + t8] =
					    ((pPmFunc_t) pobj1)->f_closure->
					    val[t8];
				}
#endif				/* HAVE_CLOSURES */

				/* Pop func obj */
				pobj3 = PM_POP();

				/* Keep ref to current frame */
				((pPmFrame_t) pobj2)->fo_back = PM_FP;

				/* Set new frame */
				PM_FP = (pPmFrame_t) pobj2;
			}

			/* If it's native func */
			else if (OBJ_GET_TYPE(((pPmFunc_t) pobj1)->f_co) ==
				 OBJ_TYPE_NOB) {
				/* Set number of locals (arguments) */
				gVmGlobal.nativeframe.nf_numlocals =
				    (uint8_t) t16;

				/* Pop args from stack */
				while (--t16 >= 0) {
					gVmGlobal.nativeframe.nf_locals[t16] =
					    PM_POP();
				}

				/* Set flag, so the GC knows a native session is active */
				gVmGlobal.nativeframe.nf_active = C_TRUE;

#ifdef HAVE_GC
				/* If the heap is low on memory, run the GC */
				if (heap_getAvail() < HEAP_GC_NF_THRESHOLD) {
					retval = heap_gcRun();
					PM_GOTO_IF_ERROR(retval,
							 CALL_FUNC_CLEANUP);
				}
#endif				/* HAVE_GC */

				/* Pop the function object */
				PM_SP--;

				/* Get native function index */
				pobj2 = (pPmObj_t) ((pPmFunc_t) pobj1)->f_co;
				t16 = ((pPmNo_t) pobj2)->no_funcindx;

				/*
				 * CALL NATIVE FXN: pass caller's frame and numargs
				 */
				/* Positive index is a stdlib func */
				if (t16 >= 0) {
					retval =
					    std_nat_fxn_table[t16] (&PM_FP);
				}

				/* Negative index is a usrlib func */
				else {
					retval =
					    usr_nat_fxn_table[-t16] (&PM_FP);
				}

				/*
				 * RETURN FROM NATIVE FXN
				 */

				/* Clear flag, so frame will not be marked by the GC */
				gVmGlobal.nativeframe.nf_active = C_FALSE;

#ifdef HAVE_CLASSES
				/* If class's __init__ called, do not push a return obj */
				if (bc == 0) {
					/* Raise TypeError if returned obj was not None */
					if ((retval == PM_RET_OK)
					    && (gVmGlobal.nativeframe.
						nf_stack != PM_NONE)) {
						PM_RAISE(retval,
							 PM_RET_EX_TYPE);
						goto CALL_FUNC_CLEANUP;
					}
				} else
#endif				/* HAVE_CLASSES */

					/* If the frame pointer was switched, do nothing to TOS */
				if (retval == PM_RET_FRAME_SWITCH) {
					retval = PM_RET_OK;
				}

				/* Otherwise, return the result from the native function */
				else {
					PM_PUSH(gVmGlobal.nativeframe.nf_stack);
				}
			}
 CALL_FUNC_CLEANUP:
			heap_gcPopTempRoot(objid);
			PM_BREAK_IF_ERROR(retval);
			continue;

		case MAKE_FUNCTION:
			/* Get num default args to fxn */
			t16 = GET_ARG();

			/*
			 * The current frame's globals become the function object's
			 * globals.  The current frame is the container object
			 * of this new function object
			 */
			retval =
			    func_new(TOS, (pPmObj_t) PM_FP->fo_globals, &pobj2);
			PM_BREAK_IF_ERROR(retval);

			/* Put any default args in a tuple */
			if (t16 > 0) {

#ifdef HAVE_DEFAULTARGS
				heap_gcPushTempRoot(pobj2, &objid);
				retval = tuple_new(t16, &pobj3);
				heap_gcPopTempRoot(objid);
				PM_BREAK_IF_ERROR(retval);
				PM_SP--;
				while (--t16 >= 0) {
					((pPmTuple_t) pobj3)->val[t16] =
					    PM_POP();
				}

				/* Set func's default args */
				((pPmFunc_t) pobj2)->f_defaultargs =
				    (pPmTuple_t) pobj3;
#else
				/* Default arguments not configured in pmfeatures.h */
				PM_RAISE(retval, PM_RET_EX_SYS);
				break;
#endif				/* HAVE_DEFAULTARGS */

			} else {
				PM_SP--;
			}

			/* Push func obj */
			PM_PUSH(pobj2);
			continue;

#ifdef HAVE_CLOSURES
		case MAKE_CLOSURE:
			/* Get number of default args */
			t16 = GET_ARG();
			retval =
			    func_new(TOS, (pPmObj_t) PM_FP->fo_globals, &pobj2);
			PM_BREAK_IF_ERROR(retval);

			/* Set closure of the new function */
			((pPmFunc_t) pobj2)->f_closure = (pPmTuple_t) TOS1;
			PM_SP -= 2;

			/* Collect any default arguments into tuple */
			if (t16 > 0) {
				heap_gcPushTempRoot(pobj2, &objid);
				retval = tuple_new(t16, &pobj3);
				heap_gcPopTempRoot(objid);
				PM_BREAK_IF_ERROR(retval);

				while (--t16 >= 0) {
					((pPmTuple_t) pobj3)->val[t16] =
					    PM_POP();
				}
				((pPmFunc_t) pobj2)->f_defaultargs =
				    (pPmTuple_t) pobj3;
			}

			/* Push new func with closure */
			PM_PUSH(pobj2);
			continue;

		case LOAD_CLOSURE:
		case LOAD_DEREF:
			/* Loads the i'th cell of free variable storage onto TOS */
			t16 = GET_ARG();
			pobj1 =
			    PM_FP->fo_locals[PM_FP->fo_func->f_co->co_nlocals +
					     t16];
			if (pobj1 == C_NULL) {
				PM_RAISE(retval, PM_RET_EX_SYS);
				break;
			}
			PM_PUSH(pobj1);
			continue;

		case STORE_DEREF:
			/* Stores TOS into the i'th cell of free variable storage */
			t16 = GET_ARG();
			PM_FP->fo_locals[PM_FP->fo_func->f_co->co_nlocals +
					 t16] = PM_POP();
			continue;
#endif				/* HAVE_CLOSURES */

		default:
			/* SystemError, unknown or unimplemented opcode */
			PM_RAISE(retval, PM_RET_EX_SYS);
			break;
		}

#ifdef HAVE_GENERATORS
		/* If got a StopIteration exception, check for a B_LOOP block */
		if (retval == PM_RET_EX_STOP) {
			pobj1 = (pPmObj_t) PM_FP;
			while ((retval == PM_RET_EX_STOP) && (pobj1 != C_NULL)) {
				pobj2 =
				    (pPmObj_t) ((pPmFrame_t) pobj1)->
				    fo_blockstack;
				while ((retval == PM_RET_EX_STOP)
				       && (pobj2 != C_NULL)) {
					if (((pPmBlock_t) pobj2)->b_type ==
					    B_LOOP) {
						/* Resume execution where the block handler says */
						/* Set PM_FP first, so PM_SP and PM_IP are set in the frame */
						PM_FP = (pPmFrame_t) pobj1;
						PM_SP =
						    ((pPmBlock_t) pobj2)->b_sp;
						PM_IP =
						    ((pPmBlock_t) pobj2)->
						    b_handler;
						((pPmFrame_t) pobj1)->
						    fo_blockstack =
						    ((pPmFrame_t) pobj1)->
						    fo_blockstack->next;
						retval = PM_RET_OK;
						break;
					}

					pobj2 =
					    (pPmObj_t) ((pPmBlock_t) pobj2)->
					    next;
				}
				pobj1 =
				    (pPmObj_t) ((pPmFrame_t) pobj1)->fo_back;
			}
			if (retval == PM_RET_OK) {
				continue;
			}
		}
#endif				/* HAVE_GENERATORS */

		/*
		 * If execution reaches this point, it is because
		 * a return value (from above) is not OK or we should exit the thread
		 * (return of the function). In any case, remove the
		 * current thread and reschedule.
		 */
		PM_REPORT_IF_ERROR(retval);

		/* If this is the last thread, return the error code */
		if ((gVmGlobal.threadList->length <= 1)
		    && (retval != PM_RET_OK)) {
			break;
		}

		retval = list_remove((pPmObj_t) gVmGlobal.threadList,
				     (pPmObj_t) gVmGlobal.pthread);
		gVmGlobal.pthread = C_NULL;
		PM_BREAK_IF_ERROR(retval);

		retval = interp_reschedule();
		PM_BREAK_IF_ERROR(retval);
	}

	return retval;
}

PmReturn_t interp_reschedule(void)
{
	PmReturn_t retval = PM_RET_OK;
	static uint8_t threadIndex = (uint8_t) 0;
	pPmObj_t pobj;

	/* If there are no threads in the runnable list, null the active thread */
	if (gVmGlobal.threadList->length == 0) {
		gVmGlobal.pthread = C_NULL;
	}

	/* Otherwise, get the next thread in the list (round robin) */
	else {
		if (++threadIndex >= gVmGlobal.threadList->length) {
			threadIndex = (uint8_t) 0;
		}
		retval =
		    list_getItem((pPmObj_t) gVmGlobal.threadList, threadIndex,
				 &pobj);
		gVmGlobal.pthread = (pPmThread_t) pobj;
		PM_RETURN_IF_ERROR(retval);
	}

	/* Clear flag to indicate a reschedule has occurred */
	interp_setRescheduleFlag(0);
	return retval;
}

PmReturn_t interp_addThread(pPmFunc_t pfunc)
{
	PmReturn_t retval;
	pPmObj_t pframe;
	pPmObj_t pthread;
	uint8_t objid1, objid2;

	/* Create a frame for the func */
	retval = frame_new((pPmObj_t) pfunc, &pframe);
	PM_RETURN_IF_ERROR(retval);

	/* Create a thread with this new frame */
	heap_gcPushTempRoot(pframe, &objid1);
	retval = thread_new(pframe, &pthread);
	if (retval != PM_RET_OK) {
		heap_gcPopTempRoot(objid1);
		return retval;
	}

	/* Add thread to end of list */
	heap_gcPushTempRoot(pthread, &objid2);
	retval = list_append((pPmObj_t) gVmGlobal.threadList, pthread);
	heap_gcPopTempRoot(objid1);
	return retval;
}

void interp_setRescheduleFlag(uint8_t boolean)
{
	gVmGlobal.reschedule = boolean;
}

